package classfile

import "fmt"

/*

   ClassFile {
       u4 magic;
       u2 minor_version;
       u2 major_version;
       u2 constant_pool_count;
       cp_info constant_pool[constant_pool_count-1];
       u2 access_flags;
       u2 this_class;
       u2 super_class;
       u2 interfaces_count;
       u2 interfaces[interfaces_count];
       u2 fields_count;
       field_info fields[fields_count];
       u2 methods_count;
       method_info methods[methods_count];
       u2 attributes_count;
       attribute_info attributes[attributes_count];
   }
*/
/*
	class字节数据的顺序与上面的结构是一样
	1： magic
	2： 次版本号
	3： 主版本号
	4： 常量池（大小与具体信息）
  	5： 类访问表示
  		是一个16位的bitmask，指出文件是类还是接口，
  		问级别是public还是private等等信息
  	6: 本类信息
  		class存储的是把点换成斜线的完全限定的名字
  		java语言规范中把这种名字称之为二进制名
  		class存储的是常量池的索引。不是类名的信息
  		很明显，只有16位，不可能存储类名字的信息

  		下面的父类，接口信息，存放的都是常量池的索引
  	7：父类信息

  	8：类实现的接口信息（接口个数与每个接口的信息）
  	9：字段信息（字段数量与每个字段的信息）
  	10：方法信息（方法数量与每个方法的信息）
  	11：属性信息（属性数量与每个属性的信息）
*/

type ClassFile struct {
	//magic uint32
	minorVersion uint16
	majorVersion uint16
	constantPool ConstantPool
	accessFlags  uint16
	thisClass    uint16
	superClass   uint16
	interfaces   []uint16
	fields       []*MemberInfo
	methods      []*MemberInfo
	attributes   []AttributeInfo
}

/*
	把class数据解析为ClassFile结构体
*/
func Parse(classData []byte) (cf *ClassFile, err error) {
	defer func() {
		if r := recover(); r != nil {
			var ok bool
			err, ok = r.(error)
			if !ok {
				err = fmt.Errorf("%v", r)
			}
		}
	}() //上面的是go语言的panic-recover机制，与对class数据的解析无关

	cr := &ClassReader{classData}
	cf = &ClassFile{} //cf是引用传值的方式传递进来的，这里赋值即可
	cf.read(cr)
	return
}

func (self *ClassFile) read(reader *ClassReader) {
	self.readAndCheckMagic(reader)
	self.readAndCheckVersion(reader)
	self.constantPool = readConstantPool(reader)
	self.accessFlags = reader.readUint16()
	self.thisClass = reader.readUint16()
	self.superClass = reader.readUint16()
	self.interfaces = reader.readUint16s()
	self.fields = readMembers(reader, self.constantPool)
	self.methods = readMembers(reader, self.constantPool)
	self.attributes = readAttributes(reader, self.constantPool)
}

//*************解析class数据的各个方法*****************************

/*
	处理文件魔数（magic number）
	许多文件格式都会认为的规定文件开头的几个固定的，没什么特殊意义的数字
	比如pdf文件以4字节的%PDF"(0x25,0x50,0x44,0x46)开头，
	ZIP文件以2字节的“PK”（0x50，0x4B)开头

	class数据的魔数以0xCAFEBABE开头
*/
func (self *ClassFile) readAndCheckMagic(reader *ClassReader) {
	magic := reader.readUint32()
	if magic != 0xCAFEBABE {
		panic("类文件格式错误：magic number")
	}
}

/*
	次版本号只在j2se 1.2之前用过，从1.2之后就没什么用了（都是0）
	主版本好在j2se之前是45，从1.2开始，每次有大的版本发布都会加1.

	特定的虚拟机实现，只能支持版本号在某个范围内的class文件，Oracle的实现是完全向后兼容的
	比如java se 8 支持版本号为45.0 - 52.0

	如果版本号不在支持的范围内，虚拟机实现就抛出java.lang.UnsupportedClassVersionError
*/
func (self *ClassFile) readAndCheckVersion(reader *ClassReader) {
	self.minorVersion = reader.readUint16()
	self.majorVersion = reader.readUint16()
	switch self.majorVersion {
	case 45:
		return
	case 46, 47, 48, 49, 50, 51, 52:
		if self.minorVersion == 0 {
			return
		}

	}
	panic("java.lang.UnsupportedClassVersionError!不支持的class版本")
}

//**************下面是6个getter方法*************
func (self *ClassFile) MinorVersion() uint16 {
	return self.minorVersion
}
func (self *ClassFile) MajorVersion() uint16 {
	return self.majorVersion
}
func (self *ClassFile) ConstantPool() ConstantPool {
	return self.constantPool
}
func (self *ClassFile) AccessFlags() uint16 {
	return self.accessFlags
}
func (self *ClassFile) Fields() []*MemberInfo {
	return self.fields
}
func (self *ClassFile) Methods() []*MemberInfo {
	return self.methods
}

//***********************经过处理后的一些有用信息*****************************

func (self *ClassFile) ClassName() string {
	return self.constantPool.getClassName(self.thisClass)
}

func (self *ClassFile) SuperClassName() string {
	if self.superClass > 0 {
		return self.constantPool.getClassName(self.superClass)
	}
	return "" //只有java.lang.Object没有父类 ，用“”表示
}

func (self *ClassFile) InterfaceNames() []string {
	interfacenNames := make([]string, len(self.interfaces))
	for i, cpIndex := range self.interfaces {
		interfacenNames[i] = self.constantPool.getClassName(cpIndex)
	}
	return interfacenNames
}
